
package com.jf.cloud.auth.controller;

import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.jf.cloud.api.auth.bo.UserInfoInTokenBO;
import com.jf.cloud.api.auth.constant.SocialType;
import com.jf.cloud.api.auth.vo.TokenInfoVO;
import com.jf.cloud.api.biz.feign.NotifyFeignClient;
import com.jf.cloud.api.rbac.dto.ClearUserPermissionsCacheDTO;
import com.jf.cloud.api.rbac.feign.PermissionFeignClient;
import com.jf.cloud.auth.config.WxConfig;
import com.jf.cloud.auth.dto.OAuthUserInfoEntity;
import com.jf.cloud.auth.manager.TokenStore;
import com.jf.cloud.auth.model.AuthAccount;
import com.jf.cloud.auth.model.AuthSocial;
import com.jf.cloud.auth.service.AuthAccountService;
import com.jf.cloud.auth.service.AuthAppService;
import com.jf.cloud.auth.service.AuthSocialService;
import com.jf.cloud.auth.vo.TokenWithTempUidVO;
import com.jf.cloud.common.response.ServerResponseEntity;
import com.jf.cloud.common.util.Json;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import ma.glasnost.orika.MapperFacade;
import me.chanjar.weixin.common.bean.WxOAuth2UserInfo;
import me.chanjar.weixin.common.bean.oauth2.WxOAuth2AccessToken;
import me.chanjar.weixin.common.error.WxErrorException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * 尝试使用社交账户登录
 * 如果登录成功，返回token信息
 * 如果登录失败，返回一个临时的uid，同时把openid之类的信息保存到数据库
 *
 * 前提：
 * 1. 如果在小程序注册，那么会直接绑定，下次打开不需要登录按钮进行登录（换了手机也会，只要是同一个微信号）
 * 2. 如果用户直接输入账号密码登录，会直接跟这个用户进行绑定，会有退出登录按钮
 * 3. 如果退出登录的话，就要解除绑定，不然没办法退出登录
 * 4. 如果用户登录过一次，其实就已经绑定了，下次进入也是直接登录，不需要输入账号密码
 * 5. 如果用户在别的微信上登录过（也就是绑定过），需要在别人的微信上进行退出登录（解除绑定），才能在新的微信上登录（换手机不影响）
 *
 * 代码逻辑：
 * 一进去小程序/微信公众号的时候会通过code获取openid（1.获取code不需要按钮的 2.code大概几分钟会过期）。
 * 如果没有openid就没办法进行支付。所以我们要在登录的时候把openid和token进行关联，通过token获取用户的userId和openid进行支付。
 * 但是会出现一种情况 比如在微信公众号是通过页面跳转获取code的，如果跳转完页面，用户一直在这个页面不去登录。后面再登录的时候已经code已经过期了。
 * 那怎么解决这种情况呢？
 *
 * 我们在拿到code的时候直接调用社交登录的接口，如/social/mp，/social/ma 这两个接口，会出现两种情况
 * 1. 如果通过code获取了openid，通过openid获取绑定的userid，如果能获取userid，那就直接登录，返回token。
 * 2. 如果没有获取到openid，就将这个openid保存用一个uuid关联起来，并保持到数据库。
 *    因为code会过期，数据库我们自己写的标识不会过期，将这个标识返回给前端（同时返回了A04001这个http状态，前端用tempUid保存起来）。
 *    前端进行登录的时候，用这个tempUid给到我们进行登录，这个时候用这个标识就能获取到数据库的openid了，也不会出现过期的情况。
 *    这个时候用openid可以关联登录信息、注册信息、绑定信息。
 *
 * @author zz
 * @date 2021/1/16
 */
@RequestMapping("/ua/social")
@RestController
@Tag(name = "社交账号登录")
public class SocialLoginController {


    @Autowired
    private WxConfig wxConfig;
    @Autowired
    private AuthSocialService authSocialService;
    @Autowired
    private TokenStore tokenStore;
    @Autowired
    private AuthAccountService authAccountService;
    @Autowired
    private MapperFacade mapperFacade;
    @Autowired
    private PermissionFeignClient permissionFeignClient;

    @Autowired
    private AuthAppService authAppService;

    private static final Logger logger = LoggerFactory.getLogger(SocialLoginController.class);


    @PostMapping("/mp")
    @Operation(summary = "公众号code登录", description = "通过公众号进行登录，只要在一进入到应用的时候调用微信登录的这个接口就行了，不要重复调用这个接口(不要在什么注册页面之类的地方再调用这个接口，打开应用的时候调用就已经足够了)" +
            "1.返回状态码 A04001 社交账号未绑定，当前端看到该异常时，将该值tempUid存起来，并在应该在合适的时间（比如在购买的时候跳），跳转到登录的页面" +
            "2.如果返回码是正常的登录成功，前端要保留原本已经存在的tempUid，如果有人切换登录的话，起码能够用这个tempUid重新获取openid进行绑定")
    public ServerResponseEntity<?> mp(@RequestBody String code) throws WxErrorException {
        Integer socialType = SocialType.MP.value();
        WxOAuth2AccessToken wxAccessToken = wxConfig.getWxMpService().getOAuth2Service().getAccessToken(code);

        AuthSocial authSocial = authSocialService.getByBizUserId(wxAccessToken.getOpenId(), socialType);
        if (authSocial != null && authSocial.getUid() != null) {
            return ServerResponseEntity.success(new TokenWithTempUidVO(authSocial.getTempUid(),getTokenVo(authSocial).getData()));
        }
        String tempUid = IdUtil.simpleUUID();
        WxOAuth2UserInfo wxUserInfo = wxConfig.getWxMpService().getOAuth2Service().getUserInfo(wxAccessToken, null);
        if (authSocial == null) {
            authSocial = new AuthSocial();
        }
        authSocial.setSocialType(socialType);
        authSocial.setTempUid(tempUid);
        authSocial.setNickName(wxUserInfo.getNickname());
        authSocial.setImageUrl(wxUserInfo.getHeadImgUrl());
        authSocial.setBizUserId(wxUserInfo.getOpenid());
        authSocial.setBizUnionid(wxUserInfo.getUnionId());
        authSocialService.saveOrUpdate(authSocial);
        // 前端要保存这个tempUid
        return ServerResponseEntity.success(new TokenWithTempUidVO(authSocial.getTempUid(), null));
    }

    @PostMapping("/appMp")
    @Operation(summary = "APPcode登录", description = "通过APP进行登录" +
            "1.返回状态码 A04001 社交账号未绑定，当前端看到该异常时，将该值tempUid存起来，并在应该在合适的时间（比如在购买的时候跳），跳转到登录的页面" +
            "2.如果返回码是正常的登录成功，前端要保留原本已经存在的tempUid，如果有人切换登录的话，起码能够用这个tempUid重新获取openid进行绑定")
    public ServerResponseEntity<?> appMp(@RequestBody String code) throws WxErrorException {
        Integer socialType = SocialType.MP.value();
        // 根据code判断是否需要重新获取用户信息
//        if(StrUtil.isEmpty(code)) {
//            logger.info(">>>Code为null调用获取code接口");
//            response.sendRedirect(config.getCode_url() + "?client_id=" + config.getClient_id() + "&response_type=code&redirect_uri=" + URLEncoder.encode(config.getRedirect_uri(), "utf-8") + "&scope=read");
//            return null;
//        }
        OAuthUserInfoEntity userInfoEntity=authAppService.getBasicInfo(code);

        WxOAuth2AccessToken wxAccessToken = wxConfig.getWxMpService().getOAuth2Service().getAccessToken(code);

        AuthSocial authSocial = authSocialService.getByBizUserId(wxAccessToken.getOpenId(), socialType);
        if (authSocial != null && authSocial.getUid() != null) {
            return ServerResponseEntity.success(new TokenWithTempUidVO(authSocial.getTempUid(),getTokenVo(authSocial).getData()));
        }
        String tempUid = IdUtil.simpleUUID();
        WxOAuth2UserInfo wxUserInfo = wxConfig.getWxMpService().getOAuth2Service().getUserInfo(wxAccessToken, null);
        if (authSocial == null) {
            authSocial = new AuthSocial();
        }
        authSocial.setSocialType(socialType);
        authSocial.setTempUid(tempUid);
        authSocial.setNickName(wxUserInfo.getNickname());
        authSocial.setImageUrl(wxUserInfo.getHeadImgUrl());
        authSocial.setBizUserId(wxUserInfo.getOpenid());
        authSocial.setBizUnionid(wxUserInfo.getUnionId());
        authSocialService.saveOrUpdate(authSocial);
        // 前端要保存这个tempUid
        return ServerResponseEntity.success(new TokenWithTempUidVO(authSocial.getTempUid(), null));
    }

    @PostMapping("/ma")
    @Operation(summary = "小程序code登录", description ="通过小程序进行登录，只要在一进入到应用的时候调用微信登录的这个接口就行了，不要重复调用这个接口(不要在什么注册页面之类的地方再调用这个接口，打开应用的时候调用就已经足够了)" +
            "1.返回状态码 A04001 社交账号未绑定，当前端看到该异常时，将该值tempUid存起来，并在应该在合适的时间（比如在购买的时候跳），跳转到登录的页面" +
            "2.如果返回码是正常的登录成功，前端要保留原本已经存在的tempUid，如果有人切换登录的话，起码能够用这个tempUid重新获取openid进行绑定")
    public ServerResponseEntity<?> ma(@RequestBody String code) throws WxErrorException {
        String recode = code.replaceAll("\"","");
        Integer socialType = SocialType.MA.value();
        WxMaJscode2SessionResult session = wxConfig.getWxMaService().getUserService().getSessionInfo(recode);

        AuthSocial authSocial = authSocialService.getByBizUserId(session.getOpenid(), socialType);
        logger.info("ma-session：{}， authSocial：{}", Json.toJsonString(session), Json.toJsonString(authSocial));
        if (authSocial != null && authSocial.getUid() != null) {
            return ServerResponseEntity.success(new TokenWithTempUidVO(authSocial.getTempUid(),getTokenVo(authSocial).getData()));
        }

        if (authSocial == null) {
            authSocial = new AuthSocial();
        }
        String tempUid = IdUtil.simpleUUID();
        authSocial.setTempUid(tempUid);
        authSocial.setSocialType(socialType);
        authSocial.setBizTempSession(session.getSessionKey());
        authSocial.setBizUserId(session.getOpenid());
        authSocial.setBizUnionid(session.getUnionid());
        authSocialService.saveOrUpdate(authSocial);
        return ServerResponseEntity.success(new TokenWithTempUidVO(authSocial.getTempUid(), null));
    }

    private ServerResponseEntity<TokenInfoVO> getTokenVo(AuthSocial authSocial) {
        AuthAccount authAccount = authAccountService.getByUid(authSocial.getUid());
        UserInfoInTokenBO data = mapperFacade.map(authAccount, UserInfoInTokenBO.class);
        data.setBizUid(authSocial.getBizUnionid());
        data.setBizUserId(authSocial.getBizUserId());
        data.setSessionKey(authSocial.getBizTempSession());
        data.setSocialType(authSocial.getSocialType());

        ClearUserPermissionsCacheDTO clearUserPermissionsCacheDTO = new ClearUserPermissionsCacheDTO();
        clearUserPermissionsCacheDTO.setSysType(data.getSysType());
        clearUserPermissionsCacheDTO.setUserId(data.getUserId());
        // 将以前的权限清理了,以免权限有缓存
        permissionFeignClient.clearUserPermissionsCache(clearUserPermissionsCacheDTO);

        // 保存token，返回token数据给前端
        return ServerResponseEntity.success(tokenStore.storeAndGetVo(data));
    }
}
